﻿/*
 
    This file is part of AnkhBG.

    AnkhBG is free software: you can redistribute it and/or modify
    it under the terms of the GNU Lesser General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.

    AnkhBG is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU Lesser General Public License for more details.

    You should have received a copy of the GNU Lesser General Public License
    along with AnkhBG.  If not, see <http://www.gnu.org/licenses/>.
 
 */


using System;
using System.Threading;

using Glider.Common.Objects;

namespace AnkhBG
{
    public class Runner
    {
        #region Public Fields

        Settings settings = Settings.Instance;

        #endregion

        #region Private Fields

        Thread thread;
        Thread AutoStartthread;

        GSpellTimer EnterCD;
        GSpellTimer AntiAFKCD = new GSpellTimer(60000);

        GSpellTimer StopTimer;
        GSpellTimer BeginDelay;

        bool enter_popup_found = true;

        private string BGProfile
        {
            get
            {
                string profile = "";
                switch (GContext.Main.ZoneText)
                {
                    case "Alterac Valley":
                        profile = settings.AVTaskDir;
                        break;
                    case "Arathi Basin":
                        profile = settings.ABTaskDir;
                        break;
                    case "Warsong Gulch":
                        profile = settings.WGTaskDir;
                        break;
                    case "Eye of the Storm":
                        profile = settings.EoTSTaskDir;
                        break;
                    default:
                        profile = "";
                        break;
                }

                return profile;
            }
        }

        private bool IsDeserter
        {
            get
            {
                GBuff[] buffs = GContext.Main.Me.GetBuffSnapshot();
                foreach (GBuff buff in buffs)
                {
                    if (buff.SpellID == 26013)
                        return true;
                }
                return false;
            }
        }

        private void AntiAFK()
        {
            if (AntiAFKCD.IsReady)
            {
                GContext.Main.SendKey("Common.Backpack");
                System.Threading.Thread.Sleep(142);
                GContext.Main.SendKey("Common.Backpack");
                AntiAFKCD.Reset();
            }
        }

        // A mutex to ensure that the thread doesn't get killed while the Cursor-Hook is not disabled
        private Object CursorHookMutex = "";

        #endregion

        #region Contructors

        public Runner()
        {
        }

        ~Runner()
        {
            try
            {
                if (GContext.Main != null && GContext.Main.IsGliderRunning)
                    GContext.Main.Debug("AnkhBG: Call: ~Runner(); Start");
                Stop();
                if (GContext.Main != null && GContext.Main.IsGliderRunning)
                    GContext.Main.Debug("AnkhBG: Call: ~Runner(); End");
            }
            catch (Exception e)
            {
                if (GContext.Main != null && GContext.Main.IsGliderRunning)
                    GContext.Main.Debug("AnkhBG: Runner::~Runner()::Exception: " + e.ToString());
            }
        }

        #endregion


        #region Public Methods

        public void Start()
        {
            thread = new Thread(new ThreadStart(RunThread));
            thread.Start();
        }

        public void Stop()
        {
            try
            {
                if (thread != null)
                {
                    if (CursorHookMutex != null && thread.IsAlive)
                    {
                        lock (CursorHookMutex)
                        {
                            thread.Abort();
                        }
                    }
                    else
                        thread.Abort();
                    thread = null;
                }
            }
            catch (Exception e)
            {
                if (GContext.Main != null && GContext.Main.IsGliderRunning)
                    GContext.Main.Debug("AnkhBG: Runner::Stop()::Exception: " + e.ToString());
            }
        }

        #endregion

        #region Private Methods

        public void AutoStart()
        {
            try
            {
                if (AutoStartthread != null && AutoStartthread.ThreadState == ThreadState.Running)
                    return;

                AutoStartthread = new Thread(new ThreadStart(RunAutoStart));
                AutoStartthread.Start();
            }
            catch (ThreadAbortException)
            {
            }
        }

        public void StopAutoStart()
        {
            if (AutoStartthread != null && AutoStartthread.IsAlive)
            {
                try
                {
                    AutoStartthread.Abort();
                }
                catch (Exception e)
                {
                    if (GContext.Main != null && GContext.Main.IsGliderRunning)
                        GContext.Main.Debug("AnkhBG: Runner::StopAutoStart()::Exception: " + e.ToString());
                }
            }
        }

        private void RunAutoStart()
        {
            while (true)
            {
                if (Main.__Instance != null)
                {
                    if (settings.AutoStart == "true")
                    {
                        if (Main.Instance.running == false && GContext.Main.IsGliding &&
                                !(
                                    GContext.Main.ZoneText == "Alterac Valley" ||
                                    GContext.Main.ZoneText == "Warsong Gulch" ||
                                    GContext.Main.ZoneText == "Eye of the Storm" ||
                                    GContext.Main.ZoneText == "Arathi Basin"
                                )
                            )
                        {
                            GContext.Main.KillAction("AnkhBG: Found Glider running when not inside a bg",false);
                        }
                        if (Main.Instance.running == false && GContext.Main.IsAttached && !GContext.Main.StoppedOnDetach)
                        {
                            Main.Instance.Window.Log("Starting AnkhBG in 10 seconds");
                            Thread.Sleep(10 * 1000);
                            Main.Instance.running = true;
                        }
                    }
                }
                Thread.Sleep(5 * 1000);
            }
        }

        private void RunThread()
        {
            try
            {
                int ret = 0;

                bool is_queued = false;

                bool loaded_profile = false;

                bool wait_for_begin_timer = true;
                bool begin_timer_started = false;
                bool fresh_battleground = false; // did we have the preparation-buff in the beginning?

                if (Int32.Parse(settings.GliderTimer) > 0)
                {
                    StopTimer = new GSpellTimer(Int32.Parse(settings.GliderTimer) * 60 * 60 * 1000);
                    StopTimer.Reset();
                }

                while (Main.__Instance != null && Main.__Instance.running && GContext.Main != null && GContext.Main.IsGliderRunning)
                {
                    if (GContext.Main.StoppedOnDetach)
                    {
                        Thread.Sleep(1000);
                        continue;
                    }

                    #region Check Timer and Marks counter
                    if (settings.GliderTimer != "" && Int32.Parse(settings.GliderTimer) > 0)
                    {
                        if (StopTimer.IsReady)
                        {
                            Main.Instance.Window.Log("Stopping after " + settings.GliderTimer + " hours");
                            break;
                        }
                    }

                    if (settings.MaxMarks != "" && Int32.Parse(settings.MaxMarks) > 0)
                    {
                        if (settings.MaxMarksBG == "AV" && (Int32.Parse(settings.MaxMarks) >= Main.Instance.Stats.AVMarks))
                        {
                            Main.Instance.Window.Log("Stopped after gained " + settings.MaxMarks + " of " + settings.MaxMarksBG);
                            break;
                        }
                        else if (settings.MaxMarksBG == "AB" && (Int32.Parse(settings.MaxMarks) >= Main.Instance.Stats.ABMarks))
                        {
                            Main.Instance.Window.Log("Stopped after gained " + settings.MaxMarks + " of " + settings.MaxMarksBG);
                            break;
                        }
                        else if (settings.MaxMarksBG == "WSG" && (Int32.Parse(settings.MaxMarks) >= Main.Instance.Stats.WGMarks))
                        {
                            Main.Instance.Window.Log("Stopped after gained " + settings.MaxMarks + " of " + settings.MaxMarksBG);
                            break;
                        }
                        else if (settings.MaxMarksBG == "EOTS" && (Int32.Parse(settings.MaxMarks) >= Main.Instance.Stats.EoTSMarks))
                        {
                            Main.Instance.Window.Log("Stopped after gained " + settings.MaxMarks + " of " + settings.MaxMarksBG);
                            break;
                        }
                    }

                    #endregion

                    // If Glider is attached
                    if (GContext.Main.IsAttached == false)
                    {
                        System.Threading.Thread.Sleep(3000);
                        continue;
                    }

                    #region Inside Battleground
                    // Check if we are inside a BG
                    if (GContext.Main.ZoneText == "Alterac Valley" ||
                        GContext.Main.ZoneText == "Warsong Gulch" ||
                        GContext.Main.ZoneText == "Eye of the Storm" ||
                        GContext.Main.ZoneText == "Arathi Basin")
                    {
                        #region BG Ended
                        // If BG end panel has pop
                        if (QueueManager.CanLeave)
                        {
                            if (GContext.Main.IsGliding)
                                GContext.Main.KillAction("AnkhBG: Leave Battleground", false);

                            // Retrieving stats from BG Score Panel
                            Main.Instance.Stats.GetStatsFromWorldScore();

                            // Now we can leave the BG
                            lock (CursorHookMutex)
                            {
                                Main.Instance.Window.Log("Leaving the battleground");
                                Main.Instance.Queue.Leave();
                                Main.Instance.Window.Log("Should have leaved now");
                            }

                            Main.Instance.Window.Log("Waiting for zone switch.");
                            System.Threading.Thread.Sleep(6000); // sleep until zoned back in
                            Main.Instance.Window.Log("Waiting done");
                        }
                        #endregion

                        #region BG Starts - Loading profiles and starts glider
                        else if (GContext.Main.IsGliding)
                        {
                            if (settings.BGAntiAFK == "true")
                                AntiAFK();
                        }
                        else
                        {
                            if (!GContext.Main.Me.IsDead) // and i'm not dead
                            {
                                if (settings.UseProfiles == "true" &&
                                        loaded_profile == false)
                                {
                                    loaded_profile = true;

                                    // get relative path
                                    string profile = BGProfile;
                                    if (BGProfile.Length > Main.path.Length &&
                                        BGProfile.Substring(0, Main.path.Length) == Main.path)
                                    { // is in glider directory
                                        profile = BGProfile.Substring(Main.path.Length + 1); // set relative path
                                    }

                                    Main.Instance.Window.Log("Loading: " + profile);

                                    if (!GContext.Main.LoadProfile(BGProfile))
                                    {
                                        Main.Instance.Window.LogError("Loading the profile failed!");
                                        break;
                                    }
                                }

                                if (GContext.Main.Me.HasBuff(44521)) // Do we have the Preparation-buff?
                                    fresh_battleground = true;

                                if (!GContext.Main.Me.HasBuff(44521) || settings.DontWait == "true")
                                {
                                    if (fresh_battleground &&
                                        wait_for_begin_timer &&
                                        settings.BeginDelay != "" &&
                                        settings.BeginDelay != "-1" &&
                                        Int32.Parse(settings.BeginDelay) > 0)
                                    {
                                        if (begin_timer_started)
                                        {
                                            if (BeginDelay.IsReady)
                                            {
                                                wait_for_begin_timer = false;
                                                GContext.Main.StartGlide();
                                            }
                                        }
                                        else
                                        {
                                            BeginDelay = new GSpellTimer(Int32.Parse(settings.BeginDelay) * 1000, true);
                                            begin_timer_started = true;

                                            Main.Instance.Window.Log("Waiting " + Int32.Parse(settings.BeginDelay) + " seconds before starting to glide!");
                                        }
                                    }
                                    else
                                        GContext.Main.StartGlide();
                                }
                            }
                        }
                        #endregion

                        #region Manage Death
                        if (GContext.Main.Me.IsDead)
                        {
                            if (GContext.Main.IsGliding) // If gliding, stop it
                                GContext.Main.KillAction("AnkhBG: Chararacter is dead", false);

                            // If we have to handle death..
                            if (settings.PatherDeaths != "true")
                            {
                                if (GContext.Main.ZoneText == "Alterac Valley" ||
                                    GContext.Main.ZoneText == "Warsong Gulch" ||
                                    GContext.Main.ZoneText == "Eye of the Storm" ||
                                    GContext.Main.ZoneText == "Arathi Basin")
                                {
                                    GContext.Main.KillAction("AnkhBG: Died", false);

                                    for (int n = 1; n <= 4; n++)
                                    {
                                        GInterfaceObject obj = GContext.Main.Interface.GetByName("StaticPopup" + n.ToString());
                                        if (obj != null && obj.IsVisible)
                                        {
                                            GInterfaceObject text = obj.GetChildObject("StaticPopup" + n.ToString() + "Text");
                                            if (text.LabelText.Contains("until release") ||
                                                text.LabelText.Contains("You have died") ||
                                                text.LabelText.Contains("Releasing"))
                                            {
                                                GInterfaceObject objB = GContext.Main.Interface.GetByName("StaticPopup" + n.ToString() + "Button1");
                                                lock (CursorHookMutex)
                                                {
                                                    GContext.Main.EnableCursorHook();
                                                    objB.Hover();
                                                    objB.ClickMouse(false);
                                                    GContext.Main.DisableCursorHook();
                                                }
                                                break;
                                            } // if LabelText.Contains()
                                        } // if obj.IsVisible
                                    } // for
                                } // if GContext.Main.ZoneText
                            } // if PPatherDeaths
                        } // if GContext.Main.Me.IsDead
                        #endregion
                    }
                    #endregion

                    #region We are not in a Battleground
                    else
                    {
                        if (GContext.Main.IsGliding) // If gliding, stop it
                            GContext.Main.KillAction("AnkhBG: Back in town", false);

                        #region We are not queued
                        if (!is_queued && !QueueManager.IsQueued) // We are not queued
                        {
                            if (!GContext.Main.StoppedOnDetach) // is zoned in?
                            {
                                if (IsDeserter)
                                {
                                    if (settings.DeserterIgnore == "true")
                                    {
                                        AntiAFK();
                                        Thread.Sleep(10 * 1000); // 10 sec
                                        continue;
                                    }
                                    else
                                    {
                                        Main.Instance.Window.LogError("Got Deserter buff... Stopping.");
                                        break;
                                    }
                                }

                                // Queueing
                                loaded_profile = false;
                                wait_for_begin_timer = true;
                                begin_timer_started = false;
                                fresh_battleground = false;

                                lock (CursorHookMutex)
                                {
                                    ret = Main.Instance.Queue.Queue();
                                }
                                if (ret > 1)
                                {
                                    GContext.Main.Debug("AnkhBG: Error: Queue() returned " + ret.ToString());
                                    break;
                                }

                                is_queued = true;
                                enter_popup_found = false;
                            }
                        }
                        #endregion

                        #region We are queued and waiting..
                        else
                        {
                            // anti-afk
                            AntiAFK();

                            if (QueueManager.CanEnter) // The enter BG frame pops up
                            {
                                if (enter_popup_found == true && EnterCD != null && EnterCD.IsReady) // Timer started and CD ready
                                {
                                    Main.Instance.Window.Log("Entering the battleground");
                                    lock (CursorHookMutex)
                                    {
                                        ret = Main.Instance.Queue.Enter();
                                    }
                                    if (ret > 1)
                                        break;

                                    is_queued = false;
                                }

                                if (enter_popup_found == false) // We are queued and BG Frame pops up, starting timer
                                {
                                    int wait_secs = 30;
                                    
                                    Random rnd = new Random();
                                    int iJoinAfterRandom = rnd.Next(settings.JoinAfterRandom);

                                    if (settings.JoinAfter != "")
                                    {
                                        int JoinAfter = Int32.Parse(settings.JoinAfter);
                                        
                                        if (JoinAfter >= 0)
                                            wait_secs = JoinAfter + iJoinAfterRandom;
                                    }
                                    EnterCD = new GSpellTimer(wait_secs * 1000);
                                    EnterCD.Reset();
                                    Main.Instance.Window.Log("Waiting base " + wait_secs.ToString() + " seconds + Max Random Seconds: " + settings.JoinAfterRandom.ToString() + " Value: " + iJoinAfterRandom.ToString());

                                    enter_popup_found = true;
                                }
                            }
                        }
                        #endregion
                    }
                    #endregion

                    System.Threading.Thread.Sleep(1000);
                }
            }
            catch (ThreadAbortException) { }
            catch (Exception e)
            {
                if (GContext.Main != null && GContext.Main.IsGliderRunning)
                    GContext.Main.Debug("AnkhBG: Runner::RunThread()::Exception: " + e.ToString());
            }
            finally
            {
                if (GContext.Main != null && GContext.Main.IsGliderRunning)
                    GContext.Main.Debug("AnkhBG: Call: RunThread::__finally;");
                if (GContext.Main != null && GContext.Main.IsGliderRunning && GContext.Main.IsGliding)
                    GContext.Main.KillAction("AnkhBG stopped", false);
                if (Main.__Instance != null && Main.__Instance.running)
                    Main.__Instance.running = false;
            }
        }
        #endregion
    }
}